﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace DXBase.Structs
{
	[StructLayout(LayoutKind.Sequential)]
	public struct Box3D : IHasBounds<Box3D>, IBounds
	{
		// this struct has the same layout as DXBase.box3d
		float x, y, z, width, height, depth;

		public Box3D(float width, float height, float depth = 1)
		{
			x = 0;
			y = 0;
			z = 0;
			if (width < 0)
				throw new ArgumentOutOfRangeException("width");
			if (height < 0)
				throw new ArgumentOutOfRangeException("height");
			if (depth < 0)
				throw new ArgumentOutOfRangeException("depth");
			this.width = width;
			this.height = height;
			this.depth = depth;
		}
		public Box3D(float x, float width, float y, float height, float z = 0, float depth = 1)
		{
			this.x = x;
			this.y = y;
			this.z = z;
			if (width < 0)
				throw new ArgumentOutOfRangeException("width");
			if (height < 0)
				throw new ArgumentOutOfRangeException("height");
			if (depth < 0)
				throw new ArgumentOutOfRangeException("depth");
			this.width = width;
			this.height = height;
			this.depth = depth;
		}
		public Box3D(params Vector3F[] points)
			: this((IEnumerable<Vector3F>)points)
		{
		}
		public Box3D(IEnumerable<Vector3F> points)
		{
			Vector3F min = new Vector3F();
			Vector3F max = new Vector3F();
			bool first = true;
			foreach (var p in points)
			{
				if (first)
				{
					first = false;
					min = max = p;
				}
				else
				{
					if (p.X < min.X)
						min.X = p.X;
					if (p.X > max.X)
						max.X = p.X;
					if (p.Y < min.Y)
						min.Y = p.Y;
					if (p.Y > max.Y)
						max.Y = p.Y;
					if (p.Z < min.Z)
						min.Z = p.Z;
					if (p.Z > max.Z)
						max.Z = p.Z;
				}
			}
			x = min.X;
			width = max.X - min.X;
			y = min.Y;
			height = max.Y - min.Y;
			z = min.Z;
			depth = max.Z - min.Z;
		}

		unsafe public static implicit operator Box3D(DXBase.box3d src) 
		{
			Box3D dst;
			*&dst = *(Box3D*)&src;
			return dst;
		}
		unsafe public static implicit operator DXBase.box3d(Box3D src)
		{
			DXBase.box3d dst;
			*&dst = *(DXBase.box3d*)&src;
			return dst;
		}
		public static explicit operator Box3D(Windows.Foundation.Rect r) { return new Box3D((float)r.X, (float)r.Width, (float)r.Y, (float)r.Height); }
		public static explicit operator Windows.Foundation.Rect(Box3D b) { return new Windows.Foundation.Rect(b.x, b.y, b.width, b.height); }

		public float X
		{
			get { return x; }
			set { x = value; }
		}
		public float Y
		{
			get { return y; }
			set { y = value; }
		}
		public float Z
		{
			get { return z; }
			set { z = value; }
		}
		public float XMax { get { return x + width; } }
		public float YMax { get { return y + height; } }
		public float ZMax { get { return z + depth; } }
		public float Width
		{
			get { return width; }
			set 
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException();
				width = value; 
			}
		}
		public float Height
		{
			get { return height; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException();
				height = value;
			}
		}
		public float Depth
		{
			get { return depth; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException();
				depth = value;
			}
		}
		public Vector3F Center { get { return new Vector3F(x + width / 2, y + height / 2, z + depth / 2); } }

		bool IBounds.Intersects(IBounds b) { return Intersects((Box3D)b); }
		public bool Intersects(Box3D r)
		{
			return
				X <= r.XMax
				&& XMax >= r.X
				&& Y <= r.YMax
				&& YMax >= r.Y
				&& Z <= r.ZMax
				&& ZMax >= r.Z
				;
		}

		bool IBounds.Contains(IBounds b) { return Contains((Box3D)b); }
		public bool Contains(Box3D r)
		{
			return
				X <= r.X
				&& XMax >= r.XMax
				&& Y <= r.Y
				&& YMax >= r.YMax
				&& Z <= r.Z
				&& ZMax >= r.ZMax
				;
		}

		float IBounds.MaxLength { get { return Math.Max(width, Math.Max(height, depth)); } }
		Box3D IHasBounds<Box3D>.Bounds { get { return this; } }

		// using array instead of IEnumerable for best performance
		public Vector3F[] GetVertices()
		{
			return new Vector3F[]
			{
				new Vector3F(x, y, z),
				new Vector3F(x + width, y, z),
				new Vector3F(x, y + height, z),
				new Vector3F(x + width, y + height, z),
				new Vector3F(x, y, z + depth),
				new Vector3F(x + width, y, z + depth),
				new Vector3F(x, y + height, z + depth),
				new Vector3F(x + width, y + height, z + depth),
			};
		}

		/// <summary>
		/// Return the bounding box of a transformed box. This is an irreversible operation.
		/// </summary>
		public Box3D Convert(Matrix4x4F m)
		{
			return new Box3D(from p in GetVertices() select m.ConvertPoint(p));
		}

		public override string ToString()
		{
			return string.Format("Box3D({0}, {1})", new Vector3F(x, y, z), new Vector3F(XMax, YMax, ZMax));
		}
	}
}
